import java.io.*;
import java.util.*;

public class KNNprocess {
    /**
     * 从txt文本中读取KNNnode所需数据并存放在List中
     * @param url  txt文本存放路径
     * @return
     */
    public List<KNNnode> ReadKNNnodeFromFile(String url){
        List<KNNnode> node = new ArrayList<KNNnode>();
        String st = "";
        File file = new File(url);
        if(file.isFile()){
            try{
                BufferedReader reader = new BufferedReader(new FileReader(file));
                while((st=reader.readLine())!=null){
                    //用空格对字符串进行分割，s+可以匹配多个空格
                    String val[] = st.split("\\s+");
                    if(val.length==3){
                        KNNnode knNnode = new KNNnode();
                        knNnode.setX1(Float.parseFloat(val[0]));
                        knNnode.setX2(Float.parseFloat(val[1]));
                        knNnode.setType(val[2]);
                        node.add(knNnode);
                    }
                }
                reader.close();
            }catch(IOException e){
                e.printStackTrace();
            }catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }
        }else{
            System.out.println("文件不存在！");
        }
        return node;
    }

    /**
     * 按照欧式距离公式，计算历史数据与待预测对象之间的距离
     * @param node l；训练集样本对象
     * @param kn1    待预测
     * @return
     */
    public List<KNNnode> calcul(List<KNNnode> node,KNNnode kn1){
        for(int i=0;i<node.size();i++){
            KNNnode kn2 = node.get(i);
            kn2.setL(Math.sqrt(Math.pow(kn1.getX1()-kn2.getX1(), 2)+Math.pow(kn1.getX2()-kn2.getX2(), 2)));
        }
        return node;
    }

    /**
     * 选取到待归类点最近的k个样本点，返回
     * @param node 所有样本点
     * @return 最近的k个样本点
     */
    private List<KNNnode> getnodeDESC(List<KNNnode> node,int k) {
        List<KNNnode> ret=new ArrayList<>();
        Collections.sort(node);
        for(int i=0;i<k;i++){
            ret.add(node.get(i));
        }
        return ret;
    }

    /**
     * 对k个KNNnode的类型进行分类统计
     * 使用Map，借助map键值对的存储方式，所以非常方便
     * @param node
     * @param k
     * @return
     */
    public Map<String,List<KNNnode>> result(List<KNNnode> node,int k){
        Map<String,List<KNNnode>> knnmap = new HashMap<String,List<KNNnode>>();
        System.out.println("---------------------K个最小的KNNnode对象-------------------");
        for(int i=0;i<node.size();i++){
            System.out.println(node.get(i).toString());
        }

        for(int i=0;i<k;i++){
            String type = node.get(i).getType().trim();
            if(knnmap.containsKey(type)){
                knnmap.get(type).add(node.get(i));
                Collections.sort(knnmap.get(type));
            }else{
                knnmap.put(type, new ArrayList<KNNnode>());
                knnmap.get(type).add(node.get(i));
            }
        }

        return knnmap;
    }

    public static void main(String args[]) {
        final String file_url = "./knn.txt";
        final int K = 5;
        String type = "";
        List<KNNnode> node = new ArrayList<KNNnode>();
        // 待归类的node
        KNNnode kn1 = new KNNnode();
        kn1.setX1(33);
        kn1.setX2(12);
        KNNprocess knNprocess = new KNNprocess();
        node = knNprocess.ReadKNNnodeFromFile(file_url);
        node = knNprocess.calcul(node, kn1);
        node = knNprocess.getnodeDESC(node, K);
        int s = 0;
        Map<String, List<KNNnode>> knn = knNprocess.result(node, K);
        for(Map.Entry<String, List<KNNnode>> en:knn.entrySet()){
            int s1= en.getValue().size();
            if(s1>s){
                s = s1;
                type = en.getKey();
            }
        }
        System.out.println("---------------------------预测结果-------------------------");
        kn1.setType(type);
        System.out.println(kn1.toString());
    }
}
